﻿using log4net;
using Microsoft.Office.Interop.Visio;
using System;
using System.Collections.Generic;
using VisioERLeveller.Model;

namespace VisioERLeveller.Visio {

    public class VisioReader : IDisposable {

        private static readonly ILog LOG = LogManager.GetLogger(typeof(VisioReader));

        readonly Application application;
        readonly Document document;
        readonly Page page;

        public Func<Shape, string> IDExtractor { get; set; }

        public Func<Shape, string> NameExtractor { get; set; }

        public Func<Shape, string> TextExtractor { get; set; }

        public Predicate<Shape> NullableLinkJudger { get; set; }

        public Predicate<Shape> NodeJudger { get; set; }

        public Predicate<Shape> LinkJudger { get; set; }

        public Func<Shape, Graph, string> FromIDFinder { get; set; }

        public Func<Shape, Graph, string> ToIDFinder { get; set; }

        public VisioReader(string filepath, string pageName) {
            try {
                this.application = new Application() {
                    Visible = false,
                };
                this.application.Settings.ShowStartupDialog = false;
                this.document = this.application.Documents.Open(filepath);
                this.page = this.document.Pages[pageName];
            } catch (Exception err) {
                LOG.Error($"Cannot open visio file {filepath} with page {pageName}", err);
                this.Dispose();
                throw err;
            }
        }

        public void Dispose() {
            this.document?.Close();
            this.application?.Quit();
        }

        public Graph ReadGraph() {
            var shapes = this.page.Shapes;
            (var nodeShapes, var linkShapes) = this.FindNodeAndLinkShapes(shapes);
            var graph = this.CreateGraph(nodeShapes, linkShapes);
            return graph;
        }

        private (IEnumerable<Shape> nodeShapes, IEnumerable<Shape> linkShapes) FindNodeAndLinkShapes(Shapes shapes) {
            var nodes = new List<Shape>();
            var links = new List<Shape>();
            foreach (var obj in shapes) {
                var shape = obj as Shape;
                if (this.NodeJudger?.Invoke(shape) ?? false)
                    nodes.Add(shape);
                if (this.LinkJudger?.Invoke(shape) ?? false)
                    links.Add(shape);
            }
            LOG.Info($"Found {nodes.Count} nodes and {links.Count} links");
            return (nodes, links);
        }

        private Graph CreateGraph(IEnumerable<Shape> nodeShapes, IEnumerable<Shape> linkShapes) {
            var graph = new Graph();
            foreach (var shape in nodeShapes) {
                var node = this.CreateNode(graph, shape);
                graph.Add(node);
            }
            foreach (var shape in linkShapes) {
                var link = this.CreateLink(graph, shape);
                graph.Add(link);
            }
            return graph;
        }

        private Node CreateNode(Graph graph, Shape shape) {
            var id = this.IDExtractor(shape);
            var name = this.NameExtractor(shape);
            var text = this.TextExtractor(shape);
            var node = new Node(graph, id, name, text);
            node.Tag(shape);
            return node;
        }

        private Link CreateLink(Graph graph, Shape shape) {
            var id = this.IDExtractor(shape);
            var name = this.NameExtractor(shape);
            var text = this.TextExtractor(shape);
            var nullable = this.NullableLinkJudger(shape);
            var from = this.FromIDFinder(shape, graph);
            var to = this.ToIDFinder(shape, graph);
            var node = new Link(graph, id, name, text, nullable, from, to);
            node.Tag(shape);
            return node;
        }
    }

}
